package com.sk.ccflower.lib.util.json;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sk.ccflower.lib.util.JSONUtil;
import com.sk.ccflower.lib.util.ReflectUtil;
import com.sk.ccflower.lib.util.reflect.AnnotationPropertyFilter;
import com.sk.ccflower.lib.util.reflect.NameArrayPropertyFilter;
import com.sk.ccflower.lib.util.reflect.PropertyFilter;

/**
 * 자바 빈 객체를 감싸  {@link JSONObject} 로 취급 될 수 있도록 하는 Wrapper 클래스. 
 * @author  dagui
 */
public class JSONWrapper extends JSONObject {
	
	private static final Log log = LogFactory.getLog(JSONWrapper.class);

	private static final long serialVersionUID = -2185692905041137153L;
	
	protected static final PropertyFilter ANNOTATION_FILTER = new AnnotationPropertyFilter(
			AffectJSON.class, AnnotationPropertyFilter.METHOD_GETTER);

	protected static Map<Package,Class<? extends JSONWrapper>> wrappers = new HashMap<Package,Class<? extends JSONWrapper>>();

	public static JSONWrapper getInstance(Object o) {
		Class<? extends JSONWrapper> wrapperClass = wrappers.get(o.getClass().getPackage());
		if (wrapperClass == null)
			return new JSONWrapper(o);
		else {
			Constructor<? extends JSONWrapper> constructor = null;
			JSONWrapper wrapper = null;
			try {
				constructor = wrapperClass.getConstructor(Object.class);
				wrapper = constructor.newInstance(o);
			}
			catch (Exception e) {
				e.printStackTrace();
			}
			return (wrapper == null)? new JSONWrapper(o): wrapper;
		}
	}
	
	/**
	 */
	protected Object object;

	/*
	 * constructor
	 */
	
	public JSONWrapper(Object value) {
		this.object = value;
	}

	/*
	 * implements JSONObject
	 */
	
	protected static final Map<Class<?>,Map<String,Method>> getterCache = new HashMap<Class<?>,Map<String,Method>>();
	
	public static Map<String, Method> findGetters(Class<? extends Object> clazz, String... propNames) {
		return findGetters(clazz, new NameArrayPropertyFilter(propNames));
	}

	public static Map<String, Method> findGetters(Class<? extends Object> clazz, PropertyFilter propFilter) {

		if (getterCache.containsKey(clazz)) {
			return getterCache.get(clazz);
		}
		else {
			synchronized (getterCache) {
				if (getterCache.containsKey(clazz)) {
					return getterCache.get(clazz);
				}
				
				// 먼저 Annotation이 지정된 메소드를 찾아보고
				PropertyDescriptor[] pds = ReflectUtil.getReadableProperties(clazz, propFilter, true);
//				if (log.isDebugEnabled()) {
//					log.debug("trying properties: " + pds.length);
//				}

				Map<String,Method> getters = new TreeMap<String,Method>();
				for (int i = 0; i < pds.length; i++) {
					PropertyDescriptor pd = pds[i];
					String name = StringEscapeUtils.escapeJavaScript(pd.getName());
					Method getter = pd.getReadMethod();
					getters.put(name, getter);
				}

				getterCache.put(clazz, getters);
				return getters;
			}
		}
	}

	@Override
	public void writeJSON(Writer out) throws IOException {
		if (object == null) {
			out.write("null");
			return;
		}
		
		Map<String,Method> getters = findGetters(object.getClass(), ANNOTATION_FILTER);

		out.write("{");
		Iterator<Map.Entry<String,Method>> it = getters.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String,Method> entry = it.next();

			out.write("\"");
			out.write(entry.getKey());
			out.write("\":");

			Method getter = entry.getValue();
			Object value = null;
			try {
				value = getPropertyValue(object, getter);
			}
			catch (Exception e) { }

			JSONUtil.writeJSON(value, out);
			if (it.hasNext())
				out.write(",");
		}
		out.write("}");
	}

	/**
	 * 이 메소드는 클래스/메소드 가시범위 관련 문제가 발생할 경우 쉽게 오버라이드 하기 위해 만들어졌음
	 */
	protected Object getPropertyValue(Object o, Method getter) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		return getter.invoke(object, new Object[]{});
	}

	@Override
	public Object getValue() {
		return object;
	}

}
